TP : Système de gestion d'une bibliothèque
Objectif général
Concevoir une application Java orientée objet qui simule la gestion des prêts d'une bibliothèque scolaire (La liste de livres provient de votre merveilleux Cégep). L'application permet aux usagers d'emprunter et de retourner des livres, tout en respectant certaines règles dépendant de leur type (étudiant, professeur, visiteur). Des opérations concurrentes (emprunts et retours simultanés) seront également simulées.
Remise
- Remettre le projet sur votre dépôt Git. Le projet doit être organisé de manière professionnelle, avec une structure de packages claire et un historique de commits reflétant le développement progressif du projet. Utiliser un
.gitignorepour éviter de committer des fichiers générés. Créer des branches pour les différentes fonctionnalités et fusionner régulièrement dansmainune fois les fonctionnalités terminées et testées. Les branches doivent être conservées jusqu'à la fin du projet pour permettre une évaluation complète de l'utilisation de Git donc ne les faites pas uniquement localement, poussez les en ligne sur votre dépôt distant (GitHub, GitLab, etc.). - Le dépôt doit contenir le code source et le fichier de sauvegarde JSON généré par l'application.
- Le projet doit compiler et s'exécuter sans modification manuelle du code source (ex. : pas de chemins d'accès codés en dur). Les instructions pour exécuter l'application doivent être clairement indiquées dans le README du dépôt. Pour le tester, télécharger le projet et lancer le dans IntelliJ, il doit s'exécuter sans problème.
Utilisation de Git
Le dépôt Git sera évalué pour la qualité du suivi de version tout au long du développement.
- Branches : le développement doit se faire sur des branches distinctes. La branche
mainne doit contenir que du code fonctionnel. - Commits réguliers : l'historique doit refléter une progression logique du projet. Un seul commit contenant tout le travail ne sera pas accepté.
- Messages de commit : clairs, concis et en lien avec les changements apportés.
Compétences visées
- Créer des classes et les organiser avec héritage et polymorphisme
- Manipuler des collections pour organiser des données
- Utiliser la programmation fonctionnelle (Stream, lambda, méthode référence) pour traiter des collections
- Gérer les accès concurrents à une ressource partagée
- Écrire des tests unitaires et des tests de concurrence pour valider le comportement du programme
- Structurer un programme Java propre et lisible
Classes à concevoir
Livre: titre, auteur, ISBN, idExemplaire (identifiant unique de l'exemplaire), statut (Disponible / Emprunté / À réparer), etatPhysique (Neuf, Bon, Usé, À réparer)Usager: nom, ID, liste d'emprunts en coursEtudiant,Professeur,Visiteur
Emprunt: référence au livre, référence à l'usager, date d'emprunt, date de retour prévueBibliotheque: contient une collection de livres et d'usagers, gère les emprunts/retours
Règles spécifiques :
- Étudiant : 3 livres max, 14 jours
- Professeur : 6 livres max, 30 jours
- Visiteur : 1 livre max, 7 jours
- Un livre dont le statut est À réparer ne peut pas être emprunté.
Gestion avec collections et programmation fonctionnelle
- Gérer un catalogue de livres.
- Gérer les emprunts des usagers. Les livres sont toujours empruntés pour la durée maximale correspondant au type de l'usager.
- Un même ouvrage (même titre, auteur, ISBN) peut exister en plusieurs exemplaires physiques. Chaque exemplaire est une instance distincte de
Livreidentifiée par sonidExemplaire. Lors du chargement du fichier MARC, le nombre d'exemplaires de chaque ouvrage est déterminé aléatoirement selon la distribution suivante :- 50 % de chance : 1 seul exemplaire
- 30 % de chance : 2 exemplaires
- 15 % de chance : 3 exemplaires
- 5 % de chance : 4 exemplaires
- Les usagers peuvent emprunter plusieurs livres, mais pas plus que leur limite autorisée. Un usager ne peut pas emprunter deux exemplaires du même ouvrage simultanément (même ISBN).
- Les retours ne peuvent s'effectuer que les jours ouvrables (lundi au vendredi). Lorsqu'un livre est retourné, il redevient disponible. Si le livre est en mauvais état, son statut passe à À réparer et il ne peut plus être emprunté. La réparation dure 3 jours ouvrables (samedi et dimanche exclus). Chaque jour ouvrable à 8h00, la bibliothèque vérifie automatiquement les exemplaires dont la date de disponibilité est atteinte et repasse leur statut à Disponible.
Exigences de programmation fonctionnelle
Les opérations suivantes doivent être implémentées en utilisant l'API Stream de Java (pas de boucles for/while) :
- Recherche : filtrer le catalogue par titre, auteur ou ISBN avec
filter()et retourner les résultats triés avecsorted(). Il est à savoir que ces recherches doivent être optimisées pour être rapides même avec 10 000 livres. - Disponibilité : obtenir la liste de tous les exemplaires disponibles à l'emprunt (statut
Disponible). - Statistiques : calculer le nombre de livres empruntés par type d'usager (étudiant, professeur, visiteur) avec
Collectors.groupingBy(). - Emprunts en retard : identifier les emprunts dont la date de retour prévue est dépassée avec
filter()etComparator. - Transformation : lors de l'affichage dans l'interface, convertir une liste de
Livreen liste de titres ou d'ISBNs avecmap().
L'utilisation de lambdas, de méthodes référence et de Optional est encouragée partout où c'est pertinent.
Action sur le catalogue
Je vous donne un fichier représentant 10 000 livres de notre bibliothèque à nous! cegep.iso2709
- Rechercher un livre (ISBN, Titre ou Auteurs). À chaque recherche, on retourne les éléments en ordre alphabétique.
- Sauvegarder à chaque fois que l'on quitte le logiciel ou par l'entremise d'un menu (JSON).
- Charger à chaque ouverture de logiciel (JSON).
- Importer un fichier MARC (offrir le choix d'ajouter à la collection actuelle ou de partir une nouvelle collection).
Format MARC / ISO 2709
Le fichier .iso2709 utilise le format ISO 2709, aussi appelé MARC (Machine-Readable Cataloging). C'est un format d'échange de notices bibliographiques. Chaque notice commence par un en-tête de 24 caractères (le leader) qui indique notamment la longueur totale de la notice. Les champs de données (titre, auteur, ISBN, etc.) sont identifiés par des codes à 3 chiffres (ex. : 245 pour le titre, 100/700 pour les auteurs, 020 pour l'ISBN).
Des bibliothèques Java comme marc4j peuvent faciliter le parsing, mais vous pourriez aussi l'implémenter manuellement en vous basant sur la spécification.
Interface à créer en JavaFX
L'utilisation de l'IA est permise et encouragée pour la partie JavaFX. La logique métier (emprunts, recherche, concurrence, persistance) doit rester votre propre travail.
- Ajout / Modification / Suppression d'un exemplaire de livre.
- Ajout / Modification / Suppression d'un utilisateur.
- Emprunt / Retour d'un exemplaire de livre. Lors du retour, l'état physique de l'exemplaire doit être renseigné (Neuf, Bon, Usé, À réparer). Si l'état est À réparer, le statut est mis à jour automatiquement.
- Rechercher un livre (par titre, par auteur ou par ISBN) : afficher si un exemplaire est disponible ou non, et si tous les exemplaires sont empruntés, afficher la date de retour la plus proche.
- Afficher le compteur global d'emprunts depuis l'ouverture.
- Afficher les exemplaires en réparation avec leur date de disponibilité prévue.
Persistance
L'état complet (livres + usagers + emprunts) est sauvegardé dans un seul fichier JSON, pour éviter les problèmes de synchronisation entre plusieurs fichiers.
- Sauvegarde automatique à la fermeture de l'application et disponible via le menu.
- Chargement automatique au démarrage.
Compteur d'emprunts
La bibliothèque doit maintenir un compteur global du nombre total d'emprunts effectués depuis le démarrage de l'application (pas le nombre en cours — le cumulatif).
- Ce compteur doit être implémenté avec
AtomicInteger. - Il doit être incrémenté à chaque emprunt réussi, même en contexte multithread.
- Son état doit être affiché dans l'interface.
Contraintes de synchronisation
- Les collections concurrentes (
ConcurrentHashMap,CopyOnWriteArrayList, etc.) sont interdites. - Les collections standard (
HashMap,ArrayList, etc.) sont permises et doivent être protégées avecsynchronized(bloc ou méthode). - La gestion des threads doit se faire via
ExecutorService, et non en créant desThreadmanuellement. RunnableetCallablesont permises pour définir les tâches soumises à l'ExecutorService.
Tests unitaires
Tests unitaires classiques
- Tester le CRUD sur les livres (en considérant les exemplaires) et les usagers.
- Tester les règles d'emprunt et de retour (limite par type d'usager, livre déjà emprunté, livre à réparer, etc.) dans des conditions normales.
- Tester la saturation des exemplaires : créer un ouvrage avec 3 exemplaires, les emprunter tous par 3 usagers distincts, puis vérifier qu'un 4e usager ne peut pas emprunter cet ouvrage (aucun exemplaire disponible) et qu'une exception ou un résultat d'échec approprié est retourné.
- Tester la libération des exemplaires : tester que lorsqu'un des 3 exemplaires est retourné, le 4e usager peut alors l'emprunter avec succès.
- Tester le calcul de la date de disponibilité après réparation : vérifier que les 5 jours ouvrables sont calculés correctement en sautant les weekends. Les retours ne pouvant avoir lieu qu'en semaine, tester un retour le lundi (3 jours consécutifs, pas de weekend) et un retour le jeudi (le weekend tombe dans le calcul) pour couvrir les deux situations.
- Tester la persistance : vérifier que les données sont correctement sauvegardées et chargées depuis le fichier JSON.
- Tester la recherche : vérifier que les recherches par titre, auteur et ISBN retournent les résultats corrects et triés.
- Tester les statistiques : vérifier que le nombre de livres empruntés par type d'usager est correctement calculé.
- Tester les emprunts en retard : créer des emprunts avec des dates de retour prévues dans le passé et vérifier que la méthode de détection les identifie tous correctement ; vérifier également qu'un emprunt dont la date de retour est dans le futur n'apparaît pas dans la liste.
Tests de la simulation multithread
Créer une classe de test SimulationTest qui utilise un ExecutorService pour soumettre plusieurs tâches représentant des usagers qui tentent d'emprunter ou retourner des livres en parallèle. La simulation fait avancer le temps jour par jour : chaque début de journée à 8h00, une tâche dédiée vérifie les exemplaires à réparer et remet leur statut à Disponible avant que les emprunts/retours de la journée ne débutent.
- Utiliser
Callablepour les tâches qui retournent un résultat etRunnablepour les tâches sans retour. - Récupérer les résultats via
Futureet vérifier qu'aucun double emprunt n'a eu lieu. - Vérifier qu'un livre ne peut pas être réservé par deux threads simultanément (test de race condition).
- Simuler l'ajout et le retrait de livres en parallèle et vérifier la cohérence du catalogue.
- Lors des retours simulés, 10 % des exemplaires reviennent abîmés : vérifier qu'à la fin de la simulation la liste des exemplaires À réparer a augmenté et que chacun possède une date de disponibilité valide.
- Simuler la suppression d'un compte avec des emprunts actifs et vérifier qu'elle est refusée.
- Créer un ouvrage avec 4 exemplaires et soumettre 6 tâches simultanées d'emprunt : vérifier que exactement 4 réussissent et 2 échouent, et que le compteur
AtomicIntegerreflète exactement 4 emprunts.
Barème de correction – TP Bibliothèque
| Catégorie | Détail | Points |
|---|---|---|
| Modélisation & structure objet | 9 | |
| Classes bien définies, attributs pertinents | 3 | |
Héritage (Usager, Étudiant, etc.) bien utilisé | 3 | |
| Encapsulation et méthodes appropriées | 3 | |
| Fonctionnalités de base | 8 | |
| Emprunt et retour selon les règles | 3 | |
| Gestion du catalogue (ajout, modif, suppression de livres) | 3 | |
| Gestion des usagers (ajout, modif, suppression avec contraintes) | 2 | |
| Interface JavaFX (IA permise) | 3 | |
| CRUD livre et usager | 1 | |
| Emprunt/retour fonctionnels | 1 | |
| Interface de recherche + compteur d'emprunts | 1 | |
| Persistance & import MARC | 5 | |
| Sauvegarde complète JSON | 2 | |
| Chargement au démarrage | 1 | |
| Import MARC fonctionnel | 2 | |
| Programmation fonctionnelle | 8 | |
Recherche et tri avec Stream + filter() + sorted() | 2 | |
Statistiques avec Collectors.groupingBy() | 2 | |
Emprunts en retard avec filter() et Comparator | 2 | |
Utilisation de lambdas, méthodes référence et Optional | 2 | |
| Concurrence | 8 | |
Utilisation correcte de synchronized dans Bibliotheque | 3 | |
Utilisation correcte de Callable et Future | 2 | |
Compteur global avec AtomicInteger | 1 | |
| Conflits bien gérés | 2 | |
| Tests unitaires | 10 | |
| Tests CRUD livres et usagers | 2 | |
| Tests emprunt/retour selon les règles (limite, livre à réparer, double emprunt) | 2 | |
| Test saturation des exemplaires (3 empruntés, 4e refusé) | 2 | |
| Test libération après retour (4e usager peut emprunter) | 1 | |
Tests de la simulation concurrente (SimulationTest avec ExecutorService) | 3 | |
| Respect des normes | Nommage, conventions Java, organisation des packages | 4 |
| Respect des normes d'utilisation de git | 10 | |
Branches créées pour chaque fonctionnalité et fusionnées dans main | 3 | |
| Commits réguliers reflétant une progression logique (min. 15 commits) | 3 | |
| Messages de commit clairs et descriptifs | 2 | |
| Branches poussées sur le dépôt distant (pas seulement local) | 1 | |
.gitignore présent et bien configuré | 1 |
Total : /65